package edu.kpi.pzks2.simulator.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import edu.kpi.pzks2.graph.Edge;
import edu.kpi.pzks2.graph.Node;
import edu.kpi.pzks2.simulator.core.router.Router;
import edu.kpi.pzks2.simulator.core.router.RouterFactory;
import edu.kpi.pzks2.simulator.core.strategy.Strategy;
import edu.kpi.pzks2.simulator.core.strategy.StrategyFactory;
import edu.kpi.pzks2.simulator.links.Channel;
import edu.kpi.pzks2.simulator.processing.Processor;
import edu.kpi.pzks2.simulator.results.SimulationResult;
import edu.kpi.pzks2.simulator.results.TimeLine;
import edu.uci.ics.jung.graph.util.Pair;


/**
 * Simulation entry point.
 *
 * @author Pustovit Michael, pustovitm@gmail.com
 */
public class Simulator {
    private static final boolean DEBUG = false;
    
    private Strategy strategy;
    private Map<Integer, Processor> processors;
    private List<Channel> channels;
    
    public Simulator(
            final SimulationBundle bundle,
            final StrategyFactory strategyFactory,
            final RouterFactory routerFactory) {
        init(bundle, strategyFactory, routerFactory);
    }

    /**
     * Initializes simulator (creates processors and links). 
     */
    private void init(
            final SimulationBundle simBundle,
            final StrategyFactory strategyFactory,
            final RouterFactory routerFactory) {
        strategy =
                strategyFactory.createStrategy(
                        simBundle.getTask(), simBundle.getSystem(), simBundle.getQueueBuilder(), this);
        final Router router = routerFactory.createRouter(simBundle.getSystem(), this);
        
        // Create processors
        processors = new HashMap<Integer, Processor>();
        for (Node node : simBundle.getSystem().getVertices()) {
            processors.put(
                    node.getId(),
                    new Processor(
                            simBundle.getProductivity(),
                            simBundle.getChannelCount(),
                            router,
                            strategy,
                            node));
        }
        
        // Create channels and link them to the processors
        channels = new ArrayList<Channel>();
        for (Edge edge : simBundle.getSystem().getEdges()) {
            final Pair<Node> procPair = simBundle.getSystem().getEndpoints(edge);
            
            final Processor procA = processors.get(procPair.getFirst().getId());
            final Processor procB = processors.get(procPair.getSecond().getId());

            final Channel channel = new Channel(
                    procA,
                    procB,
                    simBundle.getLinkSpeed(),
                    simBundle.isDuplex(),
                    simBundle.isPackageMode());
            channels.add(channel);
            
            procA.addChannel(channel.getChannel(procA));
            procB.addChannel(channel.getChannel(procB));
        }
    }
    
    /**
     * Starts simulation.
     * @return the simulation result
     */
    public SimulationResult start() {
        int safeCounter = 0;
        while (!strategy.isFinished() && safeCounter < 1000) {
            makeStep();
            safeCounter++;
        }
        
        return collectResults();
    }
    
    /**
     * Makes single simulation step.
     */
    private void makeStep() {
        strategy.makeStep();

        for (Processor proc : processors.values()) {
            proc.makeStep();
        }

        for (Channel chan : channels) {
            chan.makeStep();
        }
        
        if (DEBUG) System.out.println(collectResults());
    }
    
    /**
     * Collects results for Gantt diagrams.
     * @return the result bundle
     */
    public SimulationResult collectResults() {
        final List<TimeLine> procHist = new ArrayList<TimeLine>();
        final List<TimeLine> chanHist = new ArrayList<TimeLine>();
        
        for (Processor proc : processors.values()) {
            procHist.add(proc.getHistory());
        }
        
        for (Channel chan : channels) {
            chanHist.add(chan.getHistory());
        }
        
        return new SimulationResult(procHist, chanHist);
    }
    
    public Processor getCorrespondProcessor(final Node node) {
        return processors.get(node.getId());
    }
}